<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
<meta name="generator" content="Doxygen 1.9.4"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Tiny FTP: Tiny FTP</title>
<link href="tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="dynsections.js"></script>
<link href="search/search.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="search/searchdata.js"></script>
<script type="text/javascript" src="search/search.js"></script>
<link href="doxygen.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
 <tbody>
 <tr id="projectrow">
  <td id="projectalign">
   <div id="projectname">Tiny FTP
   </div>
  </td>
 </tr>
 </tbody>
</table>
</div>
<!-- end header part -->
<!-- Generated by Doxygen 1.9.4 -->
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&amp;dn=expat.txt MIT */
var searchBox = new SearchBox("searchBox", "search",'Search','.html');
/* @license-end */
</script>
<script type="text/javascript" src="menudata.js"></script>
<script type="text/javascript" src="menu.js"></script>
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&amp;dn=expat.txt MIT */
$(function() {
  initMenu('',true,false,'search.php','Search');
  $(document).ready(function() { init_search(); });
});
/* @license-end */
</script>
<div id="main-nav"></div>
</div><!-- top -->
<!-- window showing the filter options -->
<div id="MSearchSelectWindow"
     onmouseover="return searchBox.OnSearchSelectShow()"
     onmouseout="return searchBox.OnSearchSelectHide()"
     onkeydown="return searchBox.OnSearchSelectKey(event)">
</div>

<!-- iframe showing the search results (closed by default) -->
<div id="MSearchResultsWindow">
<iframe src="javascript:void(0)" frameborder="0" 
        name="MSearchResults" id="MSearchResults">
</iframe>
</div>

<div><div class="header">
  <div class="headertitle"><div class="title">Tiny FTP </div></div>
</div><!--header-->
<div class="contents">
<div class="textblock"><h1><a class="anchor" id="autotoc_md0"></a>
checkpoint1 实验文档</h1>
<blockquote class="doxtable">
<p >&zwj;王耕宇 17307110209 </p>
</blockquote>
<p>[toc]</p>
<h2><a class="anchor" id="autotoc_md1"></a>
编程思想</h2>
<p >程序主要利用<code>sys/socket.h</code>头文件，通过调用TCP套接字，以达到文件传输协议的实践效果。</p>
<h3><a class="anchor" id="autotoc_md2"></a>
1、程序结构</h3>
<p >根据RFC959协议，FTP有如下服务模型：</p>
<p ><img src="./pics/ftp%20model.jpg" alt="ftp model" class="inline"/></p>
<p >根据PJ的具体要求以及分工的考虑，本次代码主要包含4个文件，<code><a class="el" href="ftp__client_8c.html">ftp_client.c</a></code>，<code><a class="el" href="ftp__server_8c.html">ftp_server.c</a></code>主要用来实现套接字的控制连接、数据连接以及通过main函数调用命令接口。<code><a class="el" href="cmd__client_8c.html">cmd_client.c</a></code>、<code><a class="el" href="cmd__server_8c.html">cmd_server.c</a></code>则用于分别实现客户端和服务端的命令处理。</p>
<h3><a class="anchor" id="autotoc_md3"></a>
2、工作模式</h3>
<p >FTP 客户端和客户端必须建立不同于控制连接的新的连接用来传送数据。客户端和服务端建立数据连接时，需要考虑连接建立的方式，称为工作模式。由于数据连接不需要全程都要存在，因此在本次PJ中，数据连接主要在实现<code>ls</code>、<code>put</code>、<code>get</code>命令时建立。FTP支持两种模式：主动模式（PORT）和被动模式（PASV）。在本次PJ中，小组对以上两个模式均进行了实践。二者在控制连接过程中方式相同，均是客户端向服务器21端口发送连接请求，但在数据连接时有所不同。</p>
<ul>
<li>主动模式，本次程序默认模式。在主动模式下，客户端需要接收数据的时候在控制连接通道上发送PORT命令，并进入监听等待连接。服务器端接收到该命令后，通过自己的TCP 20端口与客户端的指定端口建立连接，发送数据。这种方式下是服务器向客户端建立数据连接。其中，客户端数据连接的端口号是大于1024的临时端口，而FTP服务器的数据端口为20。</li>
<li>被动模式，本次程序中使用<code>mode</code>命令可将工作模式切换为被动模式。Passive模式在建立数据通道时发送Pasv命令。根据RFC959协议，服务端控制进程收到被动连接请求后，将随机打开随机一个临时端口，并通过返回<code>227 entering passive mode(h1,h2,h3,h4,p1,p2)</code>信息，将端口号告知客户端控制进程，其中端口号是 $p1*256+p2$，ip 地址为<code>h1.h2.h3.h4</code>；客户端连接FTP服务器此端口，然后FTP服务器将通过这个端口进行数据的传送，这种情况下的连接请求是由客户端向服务器发起的。</li>
</ul>
<p >当数据传输完毕，服务端数据传输进程返回传输完毕，数据连接通道会关闭，只保留控制连接通道。</p>
<h3><a class="anchor" id="autotoc_md4"></a>
3、数据传输</h3>
<p >在RFC959协议中，FTP主要有三种传输模式：流模式、块模式以及压缩模式。相比于其他两种模式，流模式具有效率高，实现简单，传输数据复杂性隔离的特点，因此本次PJ数据传输主要使用流模式。具体实践中，主要依靠<code>write</code>函数向socket的文件句柄写入。</p>
<p >对于ASCII码和二进制两种文本形式，主要依靠用户在使用<code>get</code>、<code>put</code>函数时进行询问，根据用户的输入选择不同的文件打开方式以及流的读取方式。值得注意的是，只需在文件写入时进行判断，文件全部以字节流的形式进行读取。</p>
<h3><a class="anchor" id="autotoc_md5"></a>
4、访问控制</h3>
<p >由于本次pj没有并发访问等要求，因此对服务端21端口的最大监听数设置为1。对于用户的访问控制，我们在服务端存储了用户名为<code>user</code>，密码为<code>123</code>的账号，该用户拥有所有命令的操作权限，同时我们设置了匿名用户，用户名为<code>anonymous</code>，该用户登录不需要密码，但不具有<code>get</code>，<code>put</code>命令的操作权限。</p>
<h3><a class="anchor" id="autotoc_md6"></a>
5、FTP命令</h3>
<p >本次PJ中完整实现的服务端RFC959命令接口有：</p>
<p ><code>USER</code>，<code>PASS</code>，<code>CWD</code>，<code>QUIT</code>，<code>PORT</code>，<code>PASV</code>，<code>RETR</code>，<code>STOR</code>，<code>DELE</code>，<code>RMD</code>，<code>MKD</code>，<code>PWD</code>，<code>LIST</code></p>
<p >除了上述命令，为了在事件过程中达到更好的效果，还非正式地实现了传递、获取文件大小的<code>SIZE</code>指令。</p>
<p >对于客户端窗口，除了PJ文档中规定的客户端命令，我们还实现了部分本地操作命令，包括<code>lls</code>，<code>lcd</code>，<code>lpwd</code>，以便于本地用户的操作。</p>
<h3><a class="anchor" id="autotoc_md7"></a>
6、FTP响应</h3>
<p >根据RFC959中规定：</p>
<blockquote class="doxtable">
<p >&zwj;The general form of a reply is a completion code(including error codes) followed by a text string. The codes are for use by programs and the text is usually intended for human users. </p>
</blockquote>
<p>所有响应包含完成码和文本字符串，在本次PJ中，我们小组最初实施了此方案，但是由于response太多会干扰用户正常操作，同时可能污染控制信道，增加处理复杂程度，因此对于RFC959协议中规定的响应信息，我们只选择性地实施了部分，多数用于服务器程序的错误提示，在正常操作中不会出现，以提供更好的使用体验。</p>
<h2><a class="anchor" id="autotoc_md8"></a>
代码分析</h2>
<p >为了保持代码的简洁易读，我们删掉了部分消息处理、异常处理代码，只保留关键流程。由于封装了不同的层次，下面代码可能存在于不同函数中。</p>
<h3><a class="anchor" id="autotoc_md9"></a>
1、控制连接</h3>
<p ><b>客户端</b></p>
<div class="fragment"><div class="line"><span class="comment">//ftp_client.c : fill_host_addr</span></div>
<div class="line"><span class="comment">//设置服务器信息</span></div>
<div class="line"><span class="keyword">struct </span>sockaddr_in host;</div>
<div class="line">bzero(host, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> sockaddr_in));</div>
<div class="line">host-&gt;sin_family = AF_INET;     <span class="comment">//使用IPv4</span></div>
<div class="line">host-&gt;sin_addr.s_addr = inet_addr(host_ip_addr);    <span class="comment">//服务器ip</span></div>
<div class="line">host-&gt;sin_port = htons(21);     <span class="comment">//服务器控制连接端口</span></div>
<div class="line"> </div>
<div class="line"><span class="comment">//ftp_client.c : xconnect</span></div>
<div class="line"><span class="comment">//向服务器发送连接请求</span></div>
<div class="line"><span class="keyword">struct </span>timeval outtime;</div>
<div class="line">outtime.tv_sec = 3; outtime.tv_usec = 0;</div>
<div class="line"><span class="keywordtype">int</span> s = socket(AF_INET, SOCK_STREAM, 0);        <span class="comment">//创建TCP类型socket</span></div>
<div class="line"><span class="keywordtype">int</span> set = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &amp;outtime, <span class="keyword">sizeof</span>(outtime)); <span class="comment">//设置超时时间</span></div>
<div class="line">connect(s, (<span class="keyword">struct</span> sockaddr *)s_addr, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> sockaddr_in));  <span class="comment">//向控制器发送连接请求</span></div>
<div class="line"><span class="keywordflow">return</span> s;</div>
</div><!-- fragment --><p ><b>服务端</b></p>
<div class="fragment"><div class="line"><span class="comment">//ftp_server.c : create_socket</span></div>
<div class="line"><span class="keywordtype">int</span> sockfd = socket(AF_INET, SOCK_STREAM, 0);</div>
<div class="line"> </div>
<div class="line"><span class="comment">//ftp_server.c : bind_to_port</span></div>
<div class="line"><span class="comment">//将套接字绑定到该主机21端口</span></div>
<div class="line">bzero(&amp;server_addr, <span class="keyword">sizeof</span>(server_addr));</div>
<div class="line">server_addr.sin_family = AF_INET;                </div>
<div class="line">server_addr.sin_port = htons(FTP_SERVER_PORT);<span class="comment">// 设置21端口</span></div>
<div class="line">server_addr.sin_addr.s_addr = htonl(INADDR_ANY);</div>
<div class="line"><span class="keywordtype">int</span> opt = 1;</div>
<div class="line">setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &amp;opt, <span class="keyword">sizeof</span>(<span class="keywordtype">int</span>));</div>
<div class="line">bind(sockfd, (<span class="keyword">struct</span> sockaddr *)&amp;server_addr, <span class="keyword">sizeof</span>(server_addr));</div>
<div class="line"> </div>
<div class="line"><span class="comment">//ftp_server.c : listen_to_port</span></div>
<div class="line"><span class="comment">//监听21端口，等待连接</span></div>
<div class="line"><span class="keywordflow">if</span> ((listen(sockfd, backlog)) == -1){</div>
<div class="line">    perror(<span class="stringliteral">&quot;Error in listening&quot;</span>); exit(1);</div>
<div class="line">}</div>
<div class="line"> </div>
<div class="line"><span class="comment">//ftp_server.c : accept_connections</span></div>
<div class="line"><span class="comment">//获取已连接套接字</span></div>
<div class="line"><span class="keyword">struct </span>sockaddr_in client_addr;</div>
<div class="line">socklen_t len = <span class="keyword">sizeof</span>(client_addr);</div>
<div class="line"><span class="keywordtype">int</span> sockfd = accept(sock_listen, (<span class="keyword">struct</span> sockaddr *)&amp;client_addr, &amp;len);</div>
<div class="line"><span class="keywordflow">return</span> sockfd;</div>
</div><!-- fragment --><h3><a class="anchor" id="autotoc_md10"></a>
2、PORT数据连接</h3>
<p ><b>客户端</b></p>
<div class="fragment"><div class="line"><span class="comment">//ftp_client.c : xconnect_ftpdata</span></div>
<div class="line"><span class="comment">//创建socket</span></div>
<div class="line"><span class="keywordtype">int</span> get_sock = socket(AF_INET, SOCK_STREAM, 0);;</div>
<div class="line"><span class="comment">//设置随机端口</span></div>
<div class="line"><span class="keywordtype">int</span> client_port = <a class="code hl_function" href="ftp__client_8c.html#a0d2d0ee5a8e9a4e4a5f215b52d7e73c2">rand_local_port</a>();</div>
<div class="line">bzero(&amp;local_host, <span class="keyword">sizeof</span>(local_host));</div>
<div class="line">local_host.sin_family = AF_INET;</div>
<div class="line">local_host.sin_port = htons(client_port);</div>
<div class="line">local_host.sin_addr.s_addr = htonl(INADDR_ANY);</div>
<div class="line"> </div>
<div class="line"><span class="comment">//绑定本地端口</span></div>
<div class="line">set = bind(get_sock, (<span class="keyword">struct</span> sockaddr *)&amp;local_host, <span class="keyword">sizeof</span>(local_host));</div>
<div class="line"> </div>
<div class="line"><span class="comment">//监听scoket</span></div>
<div class="line">set = listen(get_sock, 1);</div>
<div class="line"> </div>
<div class="line"><span class="comment">//获取本地ip</span></div>
<div class="line"><span class="keywordflow">if</span> (getsockname(sock_control, (<span class="keyword">struct</span> sockaddr *)&amp;local, (socklen_t *)&amp;addr_len) &lt; 0)</div>
<div class="line">    <span class="keywordflow">return</span>;</div>
<div class="line">snprintf(local_ip, <span class="keyword">sizeof</span>(local_ip), <span class="stringliteral">&quot;%s&quot;</span>, inet_ntoa(local.sin_addr));</div>
<div class="line">local_ip[strlen(local_ip)] = <span class="charliteral">&#39;\0&#39;</span>; ip_1 = local_ip;</div>
<div class="line">ip_2 = strchr(local_ip, <span class="charliteral">&#39;.&#39;</span>); *ip_2 = <span class="charliteral">&#39;\0&#39;</span>; ip_2++;</div>
<div class="line">ip_3 = strchr(ip_2, <span class="charliteral">&#39;.&#39;</span>); *ip_3 = <span class="charliteral">&#39;\0&#39;</span>; ip_3++;</div>
<div class="line">ip_4 = strchr(ip_3, <span class="charliteral">&#39;.&#39;</span>); *ip_4 = <span class="charliteral">&#39;\0&#39;</span>; ip_4++;</div>
<div class="line"> </div>
<div class="line"><span class="comment">//发送PORT命令</span></div>
<div class="line">snprintf(cmd_buf, <span class="keyword">sizeof</span>(cmd_buf), <span class="stringliteral">&quot;PORT %s, %s, %s, %s, %d, %d&quot;</span>, ip_1, ip_2, ip_3, ip_4, client_port &gt;&gt; 8, client_port &amp; 0xff);</div>
<div class="line"><a class="code hl_function" href="ftp__client_8c.html#a3d3917e46b692a63525f64f0bb1c975d">ftp_send_cmd</a>(cmd_buf, NULL, sock_control);</div>
<div class="line"> </div>
<div class="line"><span class="comment">//等待连接响应</span></div>
<div class="line"><span class="keywordflow">if</span> (<a class="code hl_function" href="ftp__client_8c.html#acc60139a882577df9182dec7cf130a95">ftp_get_reply</a>(sock_control) != 200){</div>
<div class="line">    printf(<span class="stringliteral">&quot;Can not use PORT mode! Please use \&quot;mode\&quot; change to PASV mode.\n&quot;</span>);</div>
<div class="line">    <a class="code hl_function" href="ftp__client_8c.html#ae82ce0c2ad9bca58caa399c290effbca">cmd_err_exit</a>(<span class="stringliteral">&quot;socket()&quot;</span>, 1);</div>
<div class="line">} <span class="keywordflow">else</span> {</div>
<div class="line">    <span class="comment">//获取已连接数据socket</span></div>
<div class="line">    msg-&gt;ftp_data_sock = accept(get_sock, (<span class="keyword">struct</span> sockaddr *)&amp;local_host, (socklen_t *)&amp;set);</div>
<div class="line">}</div>
<div class="ttc" id="aftp__client_8c_html_a0d2d0ee5a8e9a4e4a5f215b52d7e73c2"><div class="ttname"><a href="ftp__client_8c.html#a0d2d0ee5a8e9a4e4a5f215b52d7e73c2">rand_local_port</a></div><div class="ttdeci">int rand_local_port()</div><div class="ttdoc">PORT模式下，随机生成一个端口号。</div><div class="ttdef"><b>Definition:</b> ftp_client.c:179</div></div>
<div class="ttc" id="aftp__client_8c_html_a3d3917e46b692a63525f64f0bb1c975d"><div class="ttname"><a href="ftp__client_8c.html#a3d3917e46b692a63525f64f0bb1c975d">ftp_send_cmd</a></div><div class="ttdeci">int ftp_send_cmd(const char *s1, const char *s2, int sock_fd)</div><div class="ttdoc">拼接给出的两个参数后向客户端发送。</div><div class="ttdef"><b>Definition:</b> ftp_client.c:116</div></div>
<div class="ttc" id="aftp__client_8c_html_acc60139a882577df9182dec7cf130a95"><div class="ttname"><a href="ftp__client_8c.html#acc60139a882577df9182dec7cf130a95">ftp_get_reply</a></div><div class="ttdeci">int ftp_get_reply(int sock_fd)</div><div class="ttdoc">接受来自服务端的回应。</div><div class="ttdef"><b>Definition:</b> ftp_client.c:79</div></div>
<div class="ttc" id="aftp__client_8c_html_ae82ce0c2ad9bca58caa399c290effbca"><div class="ttname"><a href="ftp__client_8c.html#ae82ce0c2ad9bca58caa399c290effbca">cmd_err_exit</a></div><div class="ttdeci">void cmd_err_exit(char *err_msg, int err_code)</div><div class="ttdoc">打印报错信息并退出。</div><div class="ttdef"><b>Definition:</b> ftp_client.c:55</div></div>
</div><!-- fragment --><p ><b>服务端</b></p>
<div class="fragment"><div class="line"><span class="comment">//cmd_server.c : handle_port</span></div>
<div class="line"><span class="comment">//获取PORT命令，接收控制端IP地址和数据端口号</span></div>
<div class="line">sscanf(msg-&gt;control_info, <span class="stringliteral">&quot;PORT %d,%d,%d,%d,%d,%d&quot;</span>, &amp;act_ip[0], &amp;act_ip[1], &amp;act_ip[2], &amp;act_ip[3], (<span class="keywordtype">int</span> *)&amp;act_port[0], (<span class="keywordtype">int</span> *)&amp;act_port[1]);</div>
<div class="line">sprintf(ip_decimal, <span class="stringliteral">&quot;%d.%d.%d.%d&quot;</span>, act_ip[0], act_ip[1], act_ip[2], act_ip[3]);</div>
<div class="line">port_dec = act_port[0] * 256 + act_port[1];</div>
<div class="line"> </div>
<div class="line">addr_data.sin_family = AF_INET; <span class="comment">// local_data_addr_act</span></div>
<div class="line">addr_data.sin_addr.s_addr = inet_addr(ip_decimal);</div>
<div class="line">addr_data.sin_port = htons(port_dec);</div>
<div class="line"> </div>
<div class="line"><span class="comment">//绑定20端口</span></div>
<div class="line">msg-&gt;ftp_data_sock = socket(AF_INET, SOCK_STREAM, 0);</div>
<div class="line">server_data.sin_family = AF_INET;</div>
<div class="line">server_data.sin_addr.s_addr = htonl(INADDR_ANY);</div>
<div class="line">server_data.sin_port = htons(20);</div>
<div class="line"><span class="keywordtype">int</span> set = bind(msg-&gt;ftp_data_sock, (<span class="keyword">struct</span> sockaddr *)&amp;server_data, <span class="keyword">sizeof</span>(server_data));</div>
<div class="line"> </div>
<div class="line"><span class="comment">//服务器主动向客户端发送连接请求</span></div>
<div class="line"><span class="keywordflow">if</span> (connect(msg-&gt;ftp_data_sock, (<span class="keyword">struct</span> sockaddr *)&amp;addr_data, (<span class="keywordtype">int</span>)<span class="keyword">sizeof</span>(<span class="keyword">struct</span> sockaddr)) != 0)</div>
<div class="line">{</div>
<div class="line">    <a class="code hl_function" href="ftp__server_8c.html#a80762bccbf172f3bcc3c6149c6c407c5">send_message</a>(client_sock, serverInfo425, <span class="stringliteral">&quot;\0&quot;</span>);</div>
<div class="line">    close(msg-&gt;ftp_data_sock);</div>
<div class="line">} <span class="keywordflow">else</span></div>
<div class="line">    <a class="code hl_function" href="ftp__server_8c.html#a80762bccbf172f3bcc3c6149c6c407c5">send_message</a>(client_sock, serverInfo200, <span class="stringliteral">&quot;\0&quot;</span>);</div>
<div class="ttc" id="aftp__server_8c_html_a80762bccbf172f3bcc3c6149c6c407c5"><div class="ttname"><a href="ftp__server_8c.html#a80762bccbf172f3bcc3c6149c6c407c5">send_message</a></div><div class="ttdeci">void send_message(int socket_control, char *info, char *extra)</div><div class="ttdoc">向客户端发送信息。</div><div class="ttdef"><b>Definition:</b> ftp_server.c:109</div></div>
</div><!-- fragment --><h3><a class="anchor" id="autotoc_md11"></a>
3、PASV数据连接</h3>
<p ><b>客户端</b></p>
<div class="fragment"><div class="line"><span class="comment">//ftp_client.c : get_port</span></div>
<div class="line"><span class="comment">//发送PASV命令并接收端口信息</span></div>
<div class="line"><a class="code hl_function" href="ftp__client_8c.html#a3d3917e46b692a63525f64f0bb1c975d">ftp_send_cmd</a>(<span class="stringliteral">&quot;PASV&quot;</span>, NULL, sock_control);</div>
<div class="line">count = read(sock_control, port_respond, 510);</div>
<div class="line"><span class="comment">//提取端口信息</span></div>
<div class="line"><span class="keywordflow">if</span> (atoi(port_respond) == 227)</div>
<div class="line">{</div>
<div class="line">    buf_ptr = strrchr(port_respond, <span class="charliteral">&#39;,&#39;</span>);</div>
<div class="line">    port_num = atoi(buf_ptr + 1);</div>
<div class="line">    buf_ptr = strrchr(port_respond, <span class="charliteral">&#39;,&#39;</span>);</div>
<div class="line">    port_num += atoi(buf_ptr + 1) * 256;</div>
<div class="line">    <span class="keywordflow">return</span> port_num;</div>
<div class="line">}</div>
<div class="line"> </div>
<div class="line"><span class="comment">//ftp_client.c : xconnect</span></div>
<div class="line"><span class="comment">//向服务器发送连接请求</span></div>
<div class="line"><span class="keyword">struct </span>timeval outtime;</div>
<div class="line">outtime.tv_sec = 3; outtime.tv_usec = 0;</div>
<div class="line"><span class="keywordtype">int</span> s = socket(AF_INET, SOCK_STREAM, 0);        <span class="comment">//创建TCP类型socket</span></div>
<div class="line"><span class="keywordtype">int</span> set = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &amp;outtime, <span class="keyword">sizeof</span>(outtime)); <span class="comment">//设置超时时间</span></div>
<div class="line">connect(s, (<span class="keyword">struct</span> sockaddr *)s_addr, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> sockaddr_in));  <span class="comment">//向控制器发送连接请求</span></div>
<div class="line"><span class="keywordflow">return</span> s;</div>
</div><!-- fragment --><p ><b>服务端</b></p>
<div class="fragment"><div class="line"><span class="comment">//cmd_server.c : create_data_sock</span></div>
<div class="line"><span class="comment">//设置随机端口</span></div>
<div class="line"><span class="keyword">struct </span>sockaddr_in t_data_addr;</div>
<div class="line">*sock = socket(AF_INET, SOCK_STREAM, 0);</div>
<div class="line">srand((<span class="keywordtype">int</span>)time(0));</div>
<div class="line"><span class="keywordtype">int</span> a = rand() % 1000 + 1025;</div>
<div class="line">bzero(&amp;t_data_addr, <span class="keyword">sizeof</span>(t_data_addr));</div>
<div class="line">t_data_addr.sin_family = AF_INET;</div>
<div class="line">t_data_addr.sin_addr.s_addr = htonl(INADDR_ANY);</div>
<div class="line">t_data_addr.sin_port = htons(a);</div>
<div class="line"> </div>
<div class="line"><span class="comment">//将socket与该端口绑定</span></div>
<div class="line"><span class="keywordflow">if</span> (bind(*sock, (<span class="keyword">struct</span> sockaddr *)&amp;t_data_addr, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> sockaddr)) &lt; 0){</div>
<div class="line">    perror(<span class="stringliteral">&quot;Bind error in create data socket:&quot;</span>);</div>
<div class="line">}</div>
<div class="line"> </div>
<div class="line"><span class="comment">//监听端口</span></div>
<div class="line">listen(*sock, 1);</div>
<div class="line"> </div>
<div class="line"><span class="keywordflow">return</span> t_data_addr;</div>
<div class="line"> </div>
<div class="line"><span class="comment">//cmd_server.c : handle_pasv</span></div>
<div class="line"><span class="comment">//FTP响应，告知客户端开放的数据端口信息（这里进行了简化，省略了ip，客户端直接获取控制连接中的ip）</span></div>
<div class="line"><span class="keywordtype">char</span> pasv_msg[MSG_INFO];</div>
<div class="line"><span class="keywordtype">char</span> port_str[8];</div>
<div class="line"><span class="keywordtype">int</span> data_sock;</div>
<div class="line"><span class="keyword">struct </span>sockaddr_in user_data_addr;</div>
<div class="line">user_data_addr = <a class="code hl_function" href="cmd__server_8c.html#a122d023237866867fcdf8ee05bf2c25c">create_data_sock</a>(&amp;data_sock);</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">int</span> tmp_port1;</div>
<div class="line"><span class="keywordtype">int</span> tmp_port2;</div>
<div class="line">tmp_port1 = ntohs(user_data_addr.sin_port) / 256;</div>
<div class="line">tmp_port2 = ntohs(user_data_addr.sin_port) % 256;</div>
<div class="line"> </div>
<div class="line"> </div>
<div class="line">snprintf(port_str, <span class="keyword">sizeof</span>(port_str), <span class="stringliteral">&quot;port,%d,%d&quot;</span>, tmp_port1, tmp_port2);</div>
<div class="line">snprintf(pasv_msg, MSG_INFO, <span class="stringliteral">&quot;227 Entering Passive Mode %s.\r\n&quot;</span>, port_str);</div>
<div class="line"><a class="code hl_function" href="ftp__server_8c.html#a80762bccbf172f3bcc3c6149c6c407c5">send_message</a>(client_sock, pasv_msg, <span class="stringliteral">&quot;\0&quot;</span>);</div>
<div class="line"> </div>
<div class="line"><span class="comment">//获取已建立的socket，数据连接建立。</span></div>
<div class="line">msg-&gt;ftp_data_sock = accept(data_sock, (<span class="keyword">struct</span> sockaddr *)&amp;user_data_addr, (socklen_t *)&amp;set);</div>
<div class="ttc" id="acmd__server_8c_html_a122d023237866867fcdf8ee05bf2c25c"><div class="ttname"><a href="cmd__server_8c.html#a122d023237866867fcdf8ee05bf2c25c">create_data_sock</a></div><div class="ttdeci">struct sockaddr_in create_data_sock(int *sock)</div><div class="ttdoc">在创建PASV连接时使用，生成传递数据所需要的套接字并完成绑定和监听。</div><div class="ttdef"><b>Definition:</b> cmd_server.c:357</div></div>
</div><!-- fragment --><h2><a class="anchor" id="autotoc_md12"></a>
屏幕截图</h2>
<h3><a class="anchor" id="autotoc_md13"></a>
1、启动</h3>
<p ><img src="./pics/start.jpg" alt="start" class="inline"/></p>
<h3><a class="anchor" id="autotoc_md14"></a>
2、查看命令</h3>
<p ><img src="./pics/help.jpg" alt="help" class="inline"/></p>
<h3><a class="anchor" id="autotoc_md15"></a>
3、测试</h3>
<p ><img src="./pics/test.jpg" alt="test" class="inline"/></p>
<h2><a class="anchor" id="autotoc_md16"></a>
鲁棒性处理</h2>
<p >本程序具有较高的鲁棒性，且实现了较好的功能，具体表现为：</p>
<ol type="1">
<li>错误指令执行help命令。</li>
<li>get检查不存在的文件，进行错误提示</li>
<li>get时检查本地是否存在同名文件并询问是否覆盖，否则退出</li>
<li>get可以进行向下的多目录级命令，如<code>get A/test.txt</code></li>
<li>get检查是否带有参数，不带参数提示错误。</li>
<li>put检查不存在的文件，进行错误提示</li>
<li>put可以进行向下的多目录级命令，如<code>put A/test.txt</code></li>
<li>put检查是否带有参数，不带参数提示错误。</li>
<li>mkdir检查是否带有参数，不带参数提示错误。</li>
<li>mkdir会检查当前目录下是否已存在同名文件夹，若存在提示错误</li>
<li>mkdir允许进行向下的多目录级命令，如<code>mkdir A/B</code></li>
<li>mkdir时若无写入权限则提示错误。</li>
<li>rmdir检查是否带有参数，不带参数提示错误。</li>
<li>rmdir会检查目标文件夹是否存在，不存在提示错误。</li>
<li>rmdir会检查目标文件夹是否为空，不为空提示错误。</li>
<li>rmdir允许进行向下的多目录级命令，如<code>rmdir A/B</code></li>
<li>delete检查是否带有参数，不带参数提示错误。</li>
<li>delete会检查目标文件是否存在，不存在提示错误。</li>
<li>delete允许进行向下的多目录级命令，如<code>delete A/B.txt</code></li>
<li>cd允许进行多目录级的命令，如<code>cd A/B</code>，<code>cd ../A</code>，<code>cd ./A</code>。</li>
<li>cd检查是否带有参数，不带参数提示错误。</li>
<li>cd会检查目标文件夹是否存在，不存在提示错误。</li>
<li>ls无视参数直接显示当前目录列表</li>
<li>lss等与命令相近的错误指令能有效检测</li>
<li>pwd无视参数直接显示当前目录</li>
<li>pwds等与命令相近的错误指令能有效检测</li>
<li>对于lpwd、lcd、lls等本地指令，同样具有相似鲁棒性。</li>
</ol>
<h2><a class="anchor" id="autotoc_md17"></a>
API Doc</h2>
<p >本项目使用Doxgen创建了html格式的文档，文档内的Files栏目下可查看各函数详细功能和参数的注解： <img src="./pics/file%20list.jpg" alt="file" class="inline"/> <img src="./pics/sample.jpg" alt="sample" class="inline"/> 文档可在html目录下的index.html中查看，或者点这里快速跳转： <a href="./html/index.html">index</a> </p>
</div></div><!-- PageDoc -->
</div><!-- contents -->
<!-- start footer part -->
<hr class="footer"/><address class="footer"><small>
Generated by&#160;<a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.4
</small></address>
</body>
</html>
